package chair;

import java.util.ArrayList;

import chair.core.Diary;
import chair.core.Stimulus;

/**
 * Va permettre de faire des associations entre des contexte enregistrés par
 * Thalamus et les "rewards" des stimuli que le robot à connu à l'intérieur. Met
 * à jour le niveau général de peur/satisfaction. À chaque rappel de contexte un
 * nouveau score est calculé suivant ce qui se passe. Le robot a la mémoire
 * courte.
 * 
 * Si l'utilisateur crée une réponse viscérale (VisceralReaction) ce sera ici
 * qu'elle sera appelée lors de chaque modification de _currentReward
 * 
 * Un contexte se présente sous la forme d'un tableau de bytes : -1 insignifiant
 * et autre valeur résultat de Organ.sense().
 * 
 * Attention: -2 est une valeur spéciale pour Thalamus, signifie que l'organe ne
 * peut rien mesurer. Ce changement est passé sous silence, mais peut apparaître
 * dans un contexte si un autre organe signale un changement pendant cette
 * sensation. (FIXME: va provoquer de l'aléatoire).
 * 
 * TODO 0: au départ je voulais interroger le modèle de conditionnement, reward
 * des US pédits par le modèle. Mais en fait mécanisme distinct : peut avoir
 * peur d'un milieu seulement parce qu'on se souveint qu'on y a reçu des chocs.
 * À défaut de "on" à tout le moins le rat. Sans CS, quelque soit le temps.
 * 
 * TODO 1: ne pas seulement retenir la récompense mais tous les US (utile pour
 * point 3)
 * 
 * TODO 2: réinitialiser nouveauté des stimulus lors changement contexte
 * 
 * TODO 3: quand retrouve ancien contexte remettre ancienne probas (on aura
 * appris à distinguer associations en fonction du contexte)
 * 
 * TODO 4: intégrer aussi les comportements, important pour point du dessus
 * 
 * TODO 5: utiliser autre chose qu'une simple addition pour MAJ reward
 * (normaliser sur -1/1)
 * 
 * TODO 6: ici "lumière" et "lumière + son" deux contextes différents, aucune
 * ressemblence
 * 
 * @author nomhad
 * 
 */
final class Hippocampus {

	/**
	 * L'indice de satisfaction générale actuel. Correspond aux récompenses des
	 * US que prédit le contexte.
	 */
	private int _currentReward = 0;
	/** Contexte actuel dans lequel se trouve le robot */
	private Context _currentContext = null;
	/**
	 * L'état de satisfaction dans lequel se trouvait le robot à la fin du
	 * précédent contexte. Permet de décider si le contexte actuel a
	 * aggravé/amélioré les choses.
	 */
	private int _previousReward = 0;
	/** Liste des ancien contextes */
	private ArrayList<Context> _contextsList;
	/**
	 * L'action viscérale éventuellememnt définie par l'utilisateur pour chaque
	 * _currentReward.
	 */
	private VisceralReaction _visceralReaction = null;

	Hippocampus() {
		_contextsList = new ArrayList<Context>();
	}

	/**
	 * Thalamus détecte un nouveau contexte, représenté sous la forme d'un
	 * vecteur de valeurs fournis par les Organes. Seulement des booléens pour
	 * le moment. Lors de l'ajout d'un contexte on enregistrera la récompense
	 * associée au précédent et on récupèrera celle déjà associé si on l'a
	 * rencontré. De même pour le cas où le nouveau contexte contient moins de
	 * valeurs significatives (nombre d'organe pertinent réduit, on sommera les
	 * anciennes connaissances)
	 * 
	 * @param context
	 *            le nouveau contexte à prendre en compte
	 */
	void newContext(Context context) {
		Diary.log("Détection contexte : ");
		Diary.logln(context.toString());

		// Si c'est notre premier contexte on l'ajoute à la liste, on le prend
		// comme contexte courant et on retourne
		if (_currentContext == null) {
			_currentContext = context;
			_contextsList.add(_currentContext);
			return;
		}

		// Changement d'humeur possible
		_previousReward = _currentReward;

		// Si le contexte est une veille connaissance on regarde dans ses petit
		// papier ce qu'on en pense, on lui donne une nouvelle chance
		int idx = _contextsList.indexOf(context);
		if (idx >= 0) {
			_currentContext = _contextsList.get(idx);
			_currentReward = _currentContext.getReward();
			Diary.logln("contexte connu, nouveau reward : " + _currentReward);
			_currentContext.setReward(0);
		} else {
			// Sinon on a peut-être déjà vu un de ses cousins
			boolean haveSeen = false;
			// On va peut-être devoir supprimer éléments de liste, travaille sur
			// copie
			Context[] contextsArray = new Context[_contextsList.size()];
			// TODO: _contextsList.toArray(contextsArray); data abort avec lejos
			for (int i = 0; i < contextsArray.length; i++) {
				contextsArray[i] = _contextsList.get(i);
			}
			for (int i = 0; i < contextsArray.length; i++) {
				Context cont = contextsArray[i];
				// Auquel cas on additionne tout ce qu'on a appris d'eux et
				// on les supprime (ils sont forcément moins précis)
				if (context.seems(cont)) {
					context.addToReward(cont.getReward());
					_contextsList.remove(cont);
					haveSeen = true;
				}
			}
			// Si c'est un contexte totalement nouveau ou la continuité de
			// l'actuel il devient simplement la nouvelle référence
			if (!haveSeen || context.seems(_currentContext)) {
				Diary.logln("contexte nouveau ou continuité");
				_currentContext = context;
				_contextsList.add(_currentContext);
			} else {
				Diary.logln("contexte similaire à un ou des anciens");
				// Ne reste plus que le cas où les contextes similaires
				// appartiennent au passé. Le robot adopte une nouvelle humeur
				// et on réinitialise le nouveau contexte.
				_currentReward = context.getReward();
				context.setReward(0);
				_currentContext = context;
				_contextsList.add(_currentContext);
			}
		}
		// Changemement de niveau de stress, met à jour état visceral
		if (_previousReward != _currentReward && _visceralReaction != null) {
			Diary.logln("Hippocampus passe VisceralReaction de "
					+ _previousReward + " à " + _currentReward);
			_visceralReaction.action(_currentReward);
		}
	}

	/**
	 * Un stimulus est survenu, on va pouvoir mettre à jour le niveau de
	 * satisfaction général et celui du contexte courant, ainsi
	 * qu'éventuellement modifier réponse viscérale. Appelé par Brain, le
	 * pendant de CoreModel.handleEvent() (seul le front montant nous intéresse
	 * ici)
	 */
	void handleStimulus(Stimulus stim) {
		int reward = stim.getReward();
		if (reward != 0) {
			// TODO: on prend le pari qu'on ne dépassera pas les bornes de int.
			// Au pire on dit que le robot subirait une toute normale
			// décompensation. Idem dans behavior calcul meilleur reward.
			_currentReward += reward;
			if (_currentContext != null)
				_currentContext.addToReward(reward);
			if (_visceralReaction != null)
				_visceralReaction.action(_currentReward);
		}
	}

	/**
	 * Permet d'accéder depuis l'extérieur à l'indice de satisfaction du robot
	 * 
	 * @return état mental courant
	 */
	int getCurrentReward() {
		return _currentReward;
	}

	/**
	 * Lors de son instanciation VisceralReaction s'enregistre. Lève une
	 * exception si on essaie de lui en faire voir de deux couleurs : pas de
	 * bipolaire ici.
	 * 
	 * @param vr
	 *            Le préposé aux viscères
	 */
	void plugVisceralReaction(VisceralReaction vr) {
		if (_visceralReaction != null)
			throw new IllegalArgumentException("VisceralReaction déjà défini");
		_visceralReaction = vr;
	}
}
